package io.gitee.zhangbinhub.admin.oauth.controller.api

import io.gitee.zhangbinhub.acp.boot.exceptions.WebException
import io.gitee.zhangbinhub.acp.boot.interfaces.LogAdapter
import io.gitee.zhangbinhub.acp.core.common.CommonTools
import io.gitee.zhangbinhub.acp.core.common.CommonTools.getDefaultCharset
import io.gitee.zhangbinhub.admin.common.api.OauthApi
import io.gitee.zhangbinhub.admin.common.permission.BaseExpression
import io.gitee.zhangbinhub.admin.common.vo.InfoVo
import io.gitee.zhangbinhub.admin.oauth.component.AuthTokenService
import io.gitee.zhangbinhub.admin.oauth.domain.ApplicationDomain
import io.gitee.zhangbinhub.admin.oauth.domain.UserDomain
import io.gitee.zhangbinhub.admin.oauth.vo.OnlineInfoVo
import io.gitee.zhangbinhub.admin.resource.server.base.BaseResourceServerController
import io.swagger.v3.oas.annotations.*
import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import io.swagger.v3.oas.annotations.tags.Tag
import jakarta.validation.constraints.NotEmpty
import jakarta.validation.constraints.NotNull
import org.bouncycastle.util.encoders.Base64
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.http.*
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication
import org.springframework.util.MultiValueMap
import org.springframework.validation.annotation.Validated
import org.springframework.web.bind.annotation.*
import org.springframework.web.client.HttpClientErrorException
import org.springframework.web.client.RestTemplate
import java.nio.charset.Charset

/**
 * @author zhang by 08/03/2019
 * @since JDK 11
 */
@Validated
@RestController
@RequestMapping(OauthApi.basePath)
@Tag(name = "认证信息")
class TokenController @Autowired
constructor(
    private val logAdapter: LogAdapter,
    private val applicationDomain: ApplicationDomain,
    private val userDomain: UserDomain,
    private val authTokenService: AuthTokenService,
    @Qualifier("acpSpringCloudResourceServerRestTemplate")
    private val restTemplate: RestTemplate,
    @Value("\${spring.application.name}")
    private val applicationName: String
) : BaseResourceServerController(logAdapter) {
    @Operation(summary = "申请token")
    @PostMapping(value = [OauthApi.token], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun applyToken(@RequestParam paramMap: MultiValueMap<String, Any>): ResponseEntity<String> {
        val clientId = paramMap.getFirst("client_id")?.toString()
        if (CommonTools.isNullStr(clientId)) {
            throw WebException("client_id 不能为空")
        }
        paramMap.remove("client_id")
        val clientSecret = paramMap.getFirst("client_secret")?.toString()
        if (CommonTools.isNullStr(clientSecret)) {
            throw WebException("client_secret 不能为空")
        }
        paramMap.remove("client_secret")
        val headers = HttpHeaders()
        headers.add("Accept", MediaType.APPLICATION_JSON_VALUE)
        headers.add("Content-type", MediaType.APPLICATION_FORM_URLENCODED_VALUE)
        headers.add(
            "Authorization",
            "Basic " + Base64.toBase64String(("$clientId:$clientSecret").toByteArray(Charset.forName(getDefaultCharset())))
        )
        return try {
            restTemplate.exchange(
                "http://$applicationName/inner/oauth/token",
                HttpMethod.POST, HttpEntity(paramMap, headers),
                String::class.java
            )
        } catch (e: HttpClientErrorException) {
            ResponseEntity.status(e.statusCode).body(e.responseBodyAsString)
        } catch (e: Exception) {
            throw WebException(HttpStatus.UNAUTHORIZED, e.message)
        }
    }

    @Operation(summary = "注销当前用户")
    @PostMapping(value = [OauthApi.logOut], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun doLogOut(@Parameter(hidden = true) bearerTokenAuthentication: BearerTokenAuthentication): ResponseEntity<InfoVo> =
        try {
            authTokenService.removeByToken(bearerTokenAuthentication.token.tokenValue)
            logAdapter.info("用户[loginNo=" + bearerTokenAuthentication.name + "]主动下线!")
            ResponseEntity.ok(InfoVo(message = "成功下线"))
        } catch (e: Exception) {
            throw WebException(e.message)
        }

    @Operation(summary = "获取各应用在线用户数统计")
    @GetMapping(value = [OauthApi.onlineInfo], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun getOnlineInfo(@Parameter(hidden = true) bearerTokenAuthentication: BearerTokenAuthentication): ResponseEntity<List<OnlineInfoVo>> =
        try {
            mutableListOf<OnlineInfoVo>().apply {
                applicationDomain.getOwnAppList(bearerTokenAuthentication).forEach {
                    this.add(
                        OnlineInfoVo(
                            appId = it.id,
                            appName = it.appName,
                            count = authTokenService.findTokensByClientId(it.id).size.toLong()
                        )
                    )
                }
            }.let {
                ResponseEntity.ok(it)
            }
        } catch (e: Exception) {
            throw WebException(e.message)
        }

    @Operation(summary = "获取用户在线情况")
    @ApiResponses(ApiResponse(responseCode = "400", description = "参数校验不通过，找不到用户信息；"))
    @PreAuthorize(BaseExpression.superOnly)
    @GetMapping(value = [OauthApi.onlineInfo + "/{userId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun getOnlineInfo(
        @Parameter(hidden = true) bearerTokenAuthentication: BearerTokenAuthentication,
        @Parameter(description = "用户id", required = true)
        @PathVariable
        userId: String
    ): ResponseEntity<List<OnlineInfoVo>> =
        try {
            mutableListOf<OnlineInfoVo>().apply {
                val userInfo = userDomain.getUserInfoById(userId) ?: throw WebException("找不到该用户信息")
                applicationDomain.getOwnAppList(bearerTokenAuthentication).forEach { item ->
                    this.add(
                        OnlineInfoVo(
                            appId = item.id,
                            appName = item.appName,
                            count = authTokenService.findTokensByClientIdAndUserName(
                                item.id,
                                userInfo.loginNo
                            ).size.toLong()
                        )
                    )
                }
            }.let {
                ResponseEntity.ok(it)
            }
        } catch (e: Exception) {
            throw WebException(e.message)
        }

    @Operation(summary = "指定应用下的用户强制下线")
    @ApiResponses(ApiResponse(responseCode = "400", description = "参数校验不通过；没有权限做此操作；"))
    @PreAuthorize(BaseExpression.superOnly)
    @DeleteMapping(value = [OauthApi.onlineInfo + "/{appId}"], produces = [MediaType.APPLICATION_JSON_VALUE])
    @Throws(WebException::class)
    fun delete(
        @Parameter(description = "应用id", required = true)
        @PathVariable
        appId: String,
        @Parameter(description = "用户id列表", required = true)
        @NotEmpty(message = "id不能为空")
        @NotNull(message = "id不能为空")
        @RequestBody
        idList: List<String>
    ): ResponseEntity<InfoVo> =
        try {
            idList.forEach {
                val userInfo = userDomain.getUserInfoById(it) ?: throw WebException("找不到该用户信息")
                authTokenService.removeTokensByClientIdAndUserName(appId, userInfo.loginNo)
                logAdapter.info("用户[" + userInfo.name + "(" + userInfo.loginNo + ")]被管理员强制下线!")
            }
            ResponseEntity.ok(InfoVo(message = "成功下线"))
        } catch (e: Exception) {
            throw WebException(e.message)
        }

}
